﻿using System;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System.Text;
using Emil.GMP;
using NUnit.Framework;

namespace BYU_ECC
{
    class RSAAlgorithm
    {
        #region RSAAlgorithm Members and Properties

        private static PrivateKey privateKey;
        private static PublicKey publicKey;
        private static int chunkSize;
        private static int paddedChunkSize;
        public const int CharSize = 8;

        #endregion

        #region Singleton Instance Functions
		private static RSAAlgorithm instance;
		public static RSAAlgorithm Instance() {
            CheckGenerated();
            return instance;
        }
		public static void GenerateRSAAlgorithm(KeySizeEnum keySize) {
			instance = new RSAAlgorithm(keySize);
        }
		private RSAAlgorithm(KeySizeEnum keySize) 
        {
            int desiredChunkSize = (int)keySize / CharSize;
            paddedChunkSize = desiredChunkSize + 2;
            chunkSize = desiredChunkSize;
            
            KeyGenerator keygen = new KeyGenerator((int)keySize);
            publicKey = keygen.PublicKey();
            privateKey = keygen.PrivateKey();
		}

		#endregion

        public byte[] Encode(string message)
        {
            ArrayList bytes = new ArrayList();

            for (long i = 0; i < message.Length; i += chunkSize)
            {
                int bytesToRead = (int)(message.Length - i > chunkSize ? chunkSize : message.Length - i);
                string chunk = message.Substring((int)i, bytesToRead);
                byte[] chunkBytes = System.Text.Encoding.Default.GetBytes(chunk);

                BigInt messageBigInt = new BigInt(chunkBytes);
                //Console.WriteLine("emessage: {0}", messageBigInt);

                //encrypt the number using public key
                BigInt cipherBigInt = messageBigInt.PowerMod(publicKey.exponent, publicKey.mod);
                //Console.WriteLine("ecipher: {0}", cipherBigInt);

                //convert the encrypted number back to bytes 
                byte[] cipherBytes = cipherBigInt.ToByteArray();

                bytes.AddRange(cipherBytes);

                //zero pad the front
                if (cipherBytes.Length < paddedChunkSize)
                {
                    bytes.AddRange(new byte[paddedChunkSize - cipherBytes.Length]);
                }
            }

            return (byte[])bytes.ToArray(typeof(byte));
        }

        public string Decode(byte[] encodedMessage)
        {
            StringBuilder outString = new StringBuilder();
            byte[] byteBuffer = new byte[paddedChunkSize];
            byte[] outbuffer = new byte[chunkSize];

            char[] charsToTrim = {'\0'};

            for (long i = 0; i < encodedMessage.Length; i += paddedChunkSize)
            {
                Array.Copy(encodedMessage, i, byteBuffer, 0, paddedChunkSize);

                //turn the bytes into a big int
                BigInt cipher = new BigInt(byteBuffer);
                //Console.WriteLine("dcipher: {0}", cipher);

                //decrypt the message using the private key
                BigInt message = cipher.PowerMod(privateKey.secret, privateKey.mod);
                //Console.WriteLine("dmessage: {0}", message);

                try
                {
                    Array.Copy(message.ToByteArray(), outbuffer, message.ToByteArray().Length);
                }
                catch (Exception e) 
                {
                    byte[] a = message.ToByteArray();
                }

                //turn the decrypted big int into a byte array 
                string toAppend = Encoding.Default.GetString(outbuffer).TrimEnd(charsToTrim);
                outString.Append(toAppend);

                outbuffer = new byte[chunkSize];
                byteBuffer = new byte[paddedChunkSize];
            }

            return outString.ToString();
        }

		private static void CheckGenerated()
        {
            if (instance == null)
				throw new EncoderNotGeneratedException();
        }
    }

    class PublicKey
    {

        public BigInt mod;
        public BigInt exponent;

        public PublicKey(BigInt modulus, BigInt publicExponent)
        {
            mod = modulus;
            exponent = publicExponent;
        }

        override public String ToString()
        {
            return "(" + mod + "," + exponent + ")";
        }

    }
    class PrivateKey
    {
        public BigInt mod;
        public BigInt secret;

        public PrivateKey(BigInt modulus, BigInt secretExponent)
        {
            mod = modulus;
            secret = secretExponent;
        }

        override public String ToString()
        {
            return "(" + mod + "," + secret + ")";
        }

    }
    class KeyGenerator
    {
        private PublicKey publicKey;
        private PrivateKey privateKey;

        public KeyGenerator(int keySize)
        {
            BigInt p = GenerateRandomBigInt(BigInt.Two.Power(keySize / 2)).NextPrimeGMP();
            BigInt q = GenerateRandomBigInt(BigInt.Two.Power(keySize / 2)).NextPrimeGMP();
            BigInt N = p * q;
            BigInt phi = (p - 1) * (q - 1);
            BigInt e = 65537;
            BigInt d = e.InvertMod(phi);

            publicKey = new PublicKey(N, e);
            privateKey = new PrivateKey(N, d);
        }

        public PublicKey PublicKey()
        {
            return publicKey;
        }

        public PrivateKey PrivateKey()
        {
            return privateKey;
        }

        public static BigInt GenerateRandomBigInt(BigInt maxSize)
        {
            StringBuilder builder;
            Random rand = new Random(DateTime.Now.Millisecond);
            BigInt ret;
            do
            {
                builder = new StringBuilder();
                int max = rand.Next(maxSize.ToString().Length - 1, maxSize.ToString().Length);
                for (int i = 0; i < max; i++)
                {
                    builder.Append((char)rand.Next('0', '9' + 1));
                }
                ret = new BigInt(builder.ToString());
            } while (ret >= maxSize);
            return ret;
        }

    }


    [TestFixture]
    class RSAAlgorithmTests
    {
        [SetUp]
        public void SetUp()
        {

        }

        [TestCase(KeySizeEnum.Size140)]
        [TestCase(KeySizeEnum.Size192)]
        [TestCase(KeySizeEnum.Size224)]
        [TestCase(KeySizeEnum.Size384)]
        [TestCase(KeySizeEnum.Size521)]
        public void TestRSAAlgorithm(KeySizeEnum keySize)
        {

            RSAAlgorithm.GenerateRSAAlgorithm(keySize);

            String message = "Four score and seven years ago";
            byte[] cipher = RSAAlgorithm.Instance().Encode(message);
            String decoded = RSAAlgorithm.Instance().Decode(cipher);

            Assert.AreEqual(message, decoded);
        }

        [TearDown]
        public void TearDown()
        {

        }

    }
}

